#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/World.h>
#include <Kharlia/Engine/Level.h>
#include <Kharlia/Engine/Actor.h>
#include <Kharlia/Engine/Engine.h>
#include <Kharlia/Engine/Player.h>
#include <Kharlia/Core/ObjectUtil.h>
#include <Kharlia/Core/App.h>
#include <Kharlia/Core/Tasker.h>

namespace kharlia { namespace engine {

using namespace py;

// Max allowed length for function names sent with RPC
static const uint8 RPC_FUNCNAME_MAX_LEN = 64;
// Max allowed length for pickled argument tuple
static const uint16 RPC_ARGSTR_MAX_LEN = 0xFFFF;
// Length of buffer allocated on stack to try to read argument string into.
// If the argument string is longer than this, memory will be allocated.
static const uint16 RPC_ARGSTR_STACK_BUFFER_LEN = 255;

//bool WriteGUID(RN::BitStream* out, object& guid) {
//    long_ guidint = extract<long_>(guid.attr("int"));
//    unsigned char bytes[16];
//    if (_PyLong_AsByteArray(reinterpret_cast<PyLongObject*>(guidint.ptr()), bytes, 16, false, false) < 0) {
//        return false;
//    }
//    out->Write(bytes, 16);
//    return true;
//}
//
//bool ReadGUID(RN::BitStream* in, object& outguid, object constructor) {
//    unsigned char bytes[16];
//    in->Read(bytes, 16);
//    long_ guidint
//}

// cPickle.dumps
static object GRPCPickleFunc;
// cPickle.HIGHEST_PROTOCOL
static object GRPCPickleProtocol;
// cPickle.loads
static object GRPCUnpickleFunc;

// -------------------------------------------------
// CGame
// -------------------------------------------------

CGame::CGame(PyObject* self, TPtr<CWorld> world):
    CObject(self),
    mWorld(world),
    mPaused(false),
    mRealTime(0.0),
    mGameTime(0.0)
{
}

void CGame::InitImpl() {
    mRealTime = 0.0;
    mGameTime = 0.0;
}

void CGame::ExitImpl() {
}

void CGame::Tick(float deltaTime) {
    mRealTime += deltaTime;
    if (!mPaused)
        mGameTime += deltaTime;
}

TPtr<CWorld> CGame::GetWorld() const {
    return mWorld.Lock();
}

//void CGame::SetWorld(shared_ptr<CWorld> const& world) {
//    mWorld = world;
//}

KH_BEGIN_GC(CGame)
KH_END_GC()

tuple CGame::Pickler::getinitargs(CGame& self) {
    return make_tuple(self.mWorld.Lock());
}

tuple CGame::Pickler::getstate(CGame& self) {
    return py::make_tuple(
        self.mPaused,
        self.mRealTime,
        self.mGameTime
    );
}

void CGame::Pickler::setstate(CGame& self, tuple state) {
    self.mPaused = extract<bool>(state[0]);
    self.mRealTime = extract<double>(state[1]);
    self.mGameTime = extract<double>(state[2]);
}

//
// CWorld
//

object CWorld::msCreateGUIDFunc;
object CWorld::msGUIDClass;
object CWorld::msLevelClass;

CWorld::CWorld(PyObject* self):
    CObject(self),
    mTicking(false),
    mInited(true),
    mIsUnbound(false),
    mGameStarted(false),
    mEngine(),
    // Network
    mNetPeer(nullptr),
    mNetDesiredPCID(rn::UNASSIGNED_NETWORK_ID)
{
    // We can't create the default level here because weak pointers can't be created
    // until the constructor is finished
    //mDefaultLevel = CreateLevel(object(), unicode(L"__Default__"), FSizeI(1, 1));
}

CWorld::~CWorld() {
    assert(!mGame);
    assert(mLevelPtrs.size() == 0);
    assert(!mNetPeer);
}

void CWorld::SetEngine(TPtr<CEngine> const& engine) {
    mEngine = engine;
}

void CWorld::AddedToEngine(TPtr<CEngine> const& engine) {
    if (!engine)
        throw ValueError("must be valid engine");
    TPtr<CEngine> current = mEngine.Lock();
    if (engine && current)
        throw RuntimeError("already bound to an engine");
    SetEngine(engine);
}

void CWorld::RemovedFromEngine(TPtr<CEngine> const& engine) {
    if (!engine)
        throw ValueError("must be valid engine");
    TPtr<CEngine> current = mEngine.Lock();
    assert(current && engine == current);
    SetEngine(TPtr<CEngine>());
}

void CWorld::ClearImpl() {
    ClearPlayers();
    StopGame();
    ClearLevels();
    NetShutdown();
}

void CWorld::Clear() {
    call_method<void>(mSelf, "Clear");
}

void CWorld::Tick(float deltaTime) {
    mTicking = true;
    // Tick levels and their actor tasklets
    try {
        if (mGame)
            mGame->Tick(deltaTime);
        for (uint i = 0; i < mLevelPtrs.size(); i++) {
            mLevelPtrs[i]->Tick(deltaTime);
        }
        // Tick network
        if (mNetPeer) {
            mNetPeer->Tick(deltaTime);

            if (mNetDesiredPCID != rn::UNASSIGNED_NETWORK_ID && mNetPeer->GetIsClient()) {
                TPtr<CActor> pc = NetGetActor(mNetDesiredPCID);
                if (pc) {
                    TPtr<CPlayer> player = GetEngine()->GetLocalPlayer();
                    if (!player->GetActor()) {
                        player->SetActor(pc);
                        pc->GetPlayerController()->SetPlayer(player);
                    }
                    mNetDesiredPCID = rn::UNASSIGNED_NETWORK_ID;
                }
            }
        }
        mTicking = false;
    }
    catch (...) {
        mTicking = false;
        throw;
    }
}

TPtr<CActor> CWorld::AddPlayer(TPtr<CPlayer> player, ENetRole::Type remoteRole) {
    assert(!mPlayers.contains(player));
    if (!GetGameStarted())
        throw RuntimeError("game has not started yet");
    mPlayers.append(player);
    return CreatePlayerActor(player, remoteRole)->GetActor();
}

void CWorld::RemovePlayer(TPtr<CPlayer> player) {
    TPtr<CActor> act = player->GetActor();
    if (act && act->GetLevel()) {
        act->GetLevel()->DestroyActor(act);
        player->SetActor(TPtr<CActor>());
    }
    if (mPlayers.contains(player))
        mPlayers.remove(player);
}

void CWorld::ClearPlayers() {
    object players(mPlayers.slice(nil, nil));
    assert(PyList_Check(players.ptr()));
    PY_FOR(bplayer, players) {
        TPtr<CPlayer> player = extract<TPtr<CPlayer>>(bplayer);
        RemovePlayer(player);
    }
    assert(len(mPlayers) == 0);
}

object CWorld::IterPlayers() const {
    return core::util::GetIter(mPlayers);
}

void CWorld::AddLevelImpl(TPtr<CLevel> level, bool check) {
    if (check && GetLevel(level->GetName()))
        throw ValueError("name already in use");
    
    object levelO(level);
    mLevels[level->GetName()] = levelO;
    mLevelHashMap[level->GetNameHash()] = levelO;
    mLevelGUIDMap[level->GetGUID()] = levelO;
    mLevelPtrs.push_back(level.GetPtr());

    level->AddedToWorld(GetSelfPtr<CWorld>());

    if (mGameStarted)
        level->StartGame();
}

void CWorld::RemoveLevelImpl(TPtr<CLevel> level) {
    assert(!mTicking);
    if (mGameStarted)
        level->StopGame();

    auto it = std::find(mLevelPtrs.begin(), mLevelPtrs.end(), level.GetPtr());
    if (it != mLevelPtrs.end())
        mLevelPtrs.erase(it);
    if (mLevels.contains(level->GetName()))
        mLevels[level->GetName()].del();
    if (mLevelHashMap.contains(level->GetNameHash()))
        mLevelHashMap[level->GetNameHash()].del();
    if (mLevelGUIDMap.contains(level->GetGUID()))
        mLevelGUIDMap[level->GetGUID()].del();
    if (mDefaultLevel == level)
        mDefaultLevel.Reset();

    level->RemovedFromWorld(GetSelfPtr<CWorld>());
}

void CWorld::ClearLevels(bool clear) {
    for (int i = mLevelPtrs.size() - 1; i > -1; i--) {
        TPtr<CLevel> level = mLevelPtrs[i]->GetSelfPtr<CLevel>();
        if (clear)
            level->Clear();
        RemoveLevel(level);
    }
}

void CWorld::StartGame() {
    if (GetGameStarted())
        return;

    if (mLevelPtrs.size() == 0)
        throw RuntimeError("no levels exist");

    // Construct and initialize the game object
    if ((!mNetPeer || mNetPeer->GetIsServer()) && !mGame) {
        if (mGameClass.is_none())
            throw RuntimeError("game class unspecified");
        mGame = extract<TPtr<CGame>>(mGameClass(GetSelfPtr<CWorld>()));
        mGame->Init();
    }

    // Notify actors that the game has started
    for (uint i = 0; i < mLevelPtrs.size(); i++) {
        mLevelPtrs[i]->StartGame();
    }

    mGameStarted = true;
}

void CWorld::StopGame() {
    if (!GetGameStarted())
        return;

    // Notify the actors that the game is stopping
    for (uint i = 0; i < mLevelPtrs.size(); i++) {
        assert(mLevelPtrs[i]->GetGameStarted());
        mLevelPtrs[i]->StopGame();
    }

    // Cleanup the game object
    if (mGame) {
        assert(!mNetPeer || mNetPeer->GetIsServer());
        mGame->Exit();
        mGame.Reset();
    }

    mGameStarted = false;
}

void CWorld::OnActorCreated(TPtr<CActor> actor, TPtr<CLevel> level) {
    //if (mGameStarted)
    //    actor->OnGameStarted();
}

void CWorld::OnActorDestroyed(TPtr<CActor> actor, TPtr<CLevel> level) {
    //if (mGameStarted)
    //    actor->OnGameStopped();
}

ENetMode CWorld::GetNetMode() {
    if (mNetPeer) {
        if (mNetPeer->GetIsClient()) {
            return NM_CLIENT;
        }
        else {
            assert(mNetPeer->GetIsServer());
            TPtr<CEngine> eng = GetEngine();
            if (eng && eng->GetClient()) {
                return NM_LISTEN_SERVER;
            }
            else {
                return NM_DEDICATED_SERVER;
            }
        }
    }
    else {
        return NM_STANDALONE;
    }
}

TPtr<CLevel> CWorld::GetLevel(object name) {
    object level;
    if (PyUnicode_CheckExact(name.ptr())) {
        level = mLevels.get(name, level);
    }
    else if (PyInt_CheckExact(name.ptr())) {
        level = mLevelHashMap.get(name, level);
    }
    else if (PyObject_IsInstance(name.ptr(), msGUIDClass.ptr())) {
        level = mLevelGUIDMap.get(name, level);
    }
    else {
        throw TypeError("level name must be unicode or int object");
    }
    if (level.is_none())
        return TPtr<CLevel>();
    return extract<TPtr<CLevel>>(level);
}

//shared_ptr<CActor> CWorld::GetActor(object levelName, object actorName) {
//    shared_ptr<CLevel> level = GetLevel(levelName);
//    if (!level)
//        return shared_ptr<CActor>();
//    return level->GetActor(actorName);
//}

void CWorld::SetLevelClass(object cls) {
    object lvlcls = GetTypeObject(type_id<CLevel>());
    assert(!lvlcls.is_none());
    if (!PyObject_IsSubclass(cls.ptr(), lvlcls.ptr()))
        throw TypeError("level class must be subtype of kharlia._engine.CLevel");
    msLevelClass = cls;
}

void CWorld::NetListen() {
    NetShutdown();
    mNetPeer = new FNetPeer();
    mNetPeer->SetHandler(this);
    mNetPeer->Listen();
    mNetPeer->InitReplicaManager(this);
}

void CWorld::NetConnected(FNetPeer* peer, rn::RakNetGUID guid, rn::SystemAddress addr) {
    NetShutdown();
    assert(peer && peer->GetIsClient() && !peer->GetReplicaManager());
    
    // Take ownership of the network interface, initialize the replica manager
    // for this world.
    mNetPeer = peer;
    mNetPeer->SetHandler(this);
    mNetPeer->InitReplicaManager(this);
    OnServerConnected(guid, addr);
}

void CWorld::NetShutdown() {
    if (mNetPeer) {
        mNetPeer->Shutdown();
        delete mNetPeer;
        mNetPeer = nullptr;
    }
}

bool CWorld::OnReceive(rn::Packet& p, rn::MessageID id) {
    bool dealloc = true;
    switch (id) {
        // Engine Messages
        case ID_DEBUG_MESSAGE:
            {
                char msg[DEBUG_MESSAGE_MAX_SIZE];
                rn::BitStream input(p.data, p.length, false);
                input.IgnoreBytes(sizeof(rn::MessageID));
                rn::StringCompressor::Instance()->DecodeString(
                    msg, DEBUG_MESSAGE_MAX_SIZE, &input);
                GetLoggerPtr()->Debug(str("Net debug message: ") + str(msg));
            }
            break;

        case ID_ACTOR_RPC:
            {
                GetLoggerPtr()->Debug("received actor RPC packet");
                rn::BitStream input(p.data, p.length, false);
                IgnorePacketIdentifier(&input);
                NetOnReceiveActorRPC(&input);
            }
            break;

        case ID_CLIENT_SETUP:
            // Sent from server to client after client successfully connects to server
            // and has a player controller created
            {
                if (mNetPeer->GetIsServer())
                    break;
                rn::BitStream input(p.data, p.length, false);
                IgnorePacketIdentifier(&input);
                rn::NetworkID nid;
                input.Read(nid);
                assert(nid != rn::UNASSIGNED_NETWORK_ID);
                assert(mNetDesiredPCID == rn::UNASSIGNED_NETWORK_ID);
                mNetDesiredPCID = nid;
            }
            break;
    }
    return dealloc;
}

void CWorld::OnClientConnected(rn::RakNetGUID guid, rn::SystemAddress addr) {
    // This is called when a client connections to a server

    // Can't accept connections if no game
    if (!GetGameStarted()) {
        mNetPeer->CloseConnection(guid, true);
        return;
    }

    // Create the player object and add it to the world
    // AddPlayer() will perform login and get us a player controller
    object cls = GetPlayerClass(false);
    TPtr<CPlayer> player = extract<TPtr<CPlayer>>(cls(false));
    TPtr<CActor> pc = AddPlayer(player, ENetRole::AUTO_PROXY);
    
    // Allocate a replica manager connection object and track it
    FReplicaManager* rm = mNetPeer->GetReplicaManager();
    auto conn = static_cast<FRMConnection*>(rm->AllocConnection(addr, guid));
    player->SetRMConnection(conn);
    conn->SetPlayer(player);
    rm->PushConnection(conn);

    // Retrieve the network ID of the newly created player controller
    assert(pc->GetNetwork());
    rn::NetworkID nid = pc->GetNetwork()->GetNetworkID();
    assert(nid != rn::UNASSIGNED_NETWORK_ID);

    // Send the ID to the client so it will knows which player controller it owns
    rn::BitStream setup;
    setup.Write((rn::MessageID)ID_CLIENT_SETUP);
    setup.Write(nid);
    mNetPeer->GetBase()->Send(&setup, HIGH_PRIORITY, RELIABLE_ORDERED, 0, guid, false);
}

void CWorld::OnClientDisconnected(rn::RakNetGUID guid, rn::SystemAddress addr, bool lost) {
    // Locate the connection and corresponding player object for the client
    FReplicaManager* rm = mNetPeer->GetReplicaManager();
    auto conn = static_cast<FRMConnection*>(rm->GetConnectionByGUID(guid));
    TPtr<CPlayer> player = conn ? conn->GetPlayer(): TPtr<CPlayer>();

    // Remove the player from the world
    if (player) {
        RemovePlayer(player);
        conn->SetPlayer(TPtr<CPlayer>());
        player->SetRMConnection(nullptr);
    }
    // Stop tracking the connection with the replica manager and destroy it
    if (conn) {
        rm->PopConnection(guid);
        rm->DeallocConnection(conn);
    }
}

void CWorld::OnServerConnected(rn::RakNetGUID guid, rn::SystemAddress addr) {
    TPtr<CPlayer> player = GetEngine()->GetLocalPlayer();
    assert(!player->GetRMConnection());
    
    // Allocate a connection for the server, and associate the local player with it
    FReplicaManager* rm = mNetPeer->GetReplicaManager();
    auto conn = static_cast<FRMConnection*>(rm->AllocConnection(addr, guid));
    player->SetRMConnection(conn);
    conn->SetPlayer(player);
    rm->PushConnection(conn);

    // Start gameplay now that we're connected
    if (!mGameStarted)
        StartGame();
}

void CWorld::OnServerDisconnected(rn::RakNetGUID guid, rn::SystemAddress addr, bool lost) {
    // Stop the gameplay
    if (mGameStarted)
        StopGame();

    // Retrieve the connection from the local player and destroy it
    FReplicaManager* rm = mNetPeer->GetReplicaManager();
    TPtr<CPlayer> player = GetEngine()->GetLocalPlayer();
    assert(player->GetRMConnection());
    assert(player->GetRMConnection()->GetRakNetGUID() == guid);
    auto conn = player->GetRMConnection();
    assert(conn->GetPlayer() == player);
    conn->SetPlayer(TPtr<CPlayer>());
    player->SetRMConnection(nullptr);
    rm->PopConnection(guid); // Replicas dealloc'd here
    rm->DeallocConnection(conn);
}

//void CWorld::NetSendDebugMessage(str msg) {
    //assert(mNetPeer);
    //rn::BitStream output;

    //output.Write((rn::MessageID)ID_DEBUG_MESSAGE);
    //rn::StringCompressor::Instance()->EncodeString(
    //    extract<char*>(msg), DEBUG_MESSAGE_MAX_SIZE, &output);

    //mNetPeer->GetBase()->Send(&output, HIGH_PRIORITY, RELIABLE_ORDERED,
    //                          0, mNetPeer->GetServerConnection(), true);
//}

void CWorld::NetSendActorRPC(bool reliable, TPtr<CActor> actor, str funcname, tuple args) {
    // We're not supporting specific destinations yet for this test, so
    // make sure we have something to send to in case of server->client
    //if (!mNetPeer ||
    //    (GetNetIsClient() && mNetServerConnection == rn::UNASSIGNED_RAKNET_GUID) ||
    //    (!GetNetIsClient() && mNetClientConnections.size() < 1)) {
    //    throw RuntimeError("cannot send actor RPC, destination unavailable");
    //}

    //// Destination should already exist, so use hashes for names where possible
    //uint8 funcNameLen = len(funcname);
    //sint32 nameHash = actor->GetNameHashAsInt();
    //sint32 levelHash = actor->GetLevel()->GetNameHashAsInt();
    //uint8 arity = len(args);
    //object argStr;
    //uint16 argStrLen = 0;

    //if (funcNameLen > RPC_FUNCNAME_MAX_LEN)
    //    throw RuntimeError("function name too long");
    //else if (funcNameLen < 1)
    //    throw RuntimeError("function name is empty");

    //if (arity > 0) {
    //    argStr = GRPCPickleFunc(args, GRPCPickleProtocol);
    //    assert(PyString_CheckExact(argStr.ptr()));
    //    argStrLen = PyString_GET_SIZE(argStr.ptr());
    //    if (argStrLen > RPC_ARGSTR_MAX_LEN)
    //        throw RuntimeError("pickled argument string too large");
    //}
    //
    //// Create our packet:
    //// id:1, levelhash:4, namehash:4, funcname:1x, args:2x
    //rn::BitStream out;
    //out.Write((rn::MessageID)ID_ACTOR_RPC);
    //out.Write(levelHash);
    //out.Write(nameHash);

    //out.Write(funcNameLen);
    //out.WriteAlignedBytes((uchar*)PyString_AS_STRING(funcname.ptr()), funcNameLen);

    //if (arity > 0) {
    //    out.Write(argStrLen);
    //    out.WriteAlignedBytes((uchar*)PyString_AS_STRING(argStr.ptr()), argStrLen);
    //}
    //
    //PacketReliability rel = reliable? RELIABLE: UNRELIABLE;
    //// For now, only send to first client for testing purposes
    //rn::RakNetGUID dest = GetNetIsClient()? mNetServerConnection: mNetClientConnections[0];
    //uint32 result = mNetPeer->Send(&out, MEDIUM_PRIORITY, rel, 0, dest, false);
    //if (result == 0)
    //    throw RuntimeError("bad input for RakPeer::Send");
}

void CWorld::NetOnReceiveActorRPC(rn::BitStream* in) {
    //sint32 hashTemp;
    //char funcNameBuf[RPC_FUNCNAME_MAX_LEN],
    //     argsStrBuf[RPC_ARGSTR_STACK_BUFFER_LEN];
    //uint8 funcNameLen;
    //uint16 argStrLen;
    //object levelHash, actorHash, funcName, argStr;
    //tuple args;

    //in->Read(hashTemp);
    //levelHash = object(hashTemp);
    //in->Read(hashTemp);
    //actorHash = object(hashTemp);

    //in->Read(funcNameLen);
    //if (funcNameLen > RPC_FUNCNAME_MAX_LEN)
    //    throw RuntimeError("function name too long on received actor RPC");
    //assert(funcNameLen > 0);
    //in->ReadAlignedBytes((uchar*)funcNameBuf, funcNameLen);
    //funcName = str(funcNameBuf, funcNameLen);

    //in->Read(argStrLen);
    //if (argStrLen > 0) {
    //    if (argStrLen > RPC_ARGSTR_MAX_LEN)
    //        throw RuntimeError("rpc args str is too long");
    //    // Read string to stack if possible
    //    if (argStrLen <= RPC_ARGSTR_STACK_BUFFER_LEN) {
    //        in->ReadAlignedBytes((uchar*)argsStrBuf, argStrLen);
    //        argStr = str(argsStrBuf, argStrLen);
    //    }
    //    else {
    //        scoped_array<char> buf(new char[argStrLen]);
    //        in->ReadAlignedBytes((uchar*)buf.get(), argStrLen);
    //        argStr = str(buf.get(), argStrLen);
    //    }
    //    args = extract<tuple>(GRPCUnpickleFunc(argStr));
    //}

    //shared_ptr<CActor> actor = GetActor(levelHash, actorHash);
    //if (!actor)
    //    throw RuntimeError("target actor not found");
    //
    //// Tasklet out the call
    //const shared_ptr<core::CTasker>& atm = actor->GetLevel()->GetTasker();
    //object meth = actor->GetSelfAttr(funcName);
    //object tasklet = atm->CreateTasklet(meth);
    //handle<>(PyObject_Call(tasklet.ptr(), args.ptr(), NULL));
    //atm->InsertTasklet(tasklet, CLevel::PRE_TICK_GROUP);
}

TPtr<CActor> CWorld::NetGetActor(rn::NetworkID id) {
    if (!mNetPeer || !mNetPeer->GetReplicaManager())
        return TPtr<CActor>();
    rn::Replica3* p = mNetPeer->GetReplicaManager()->GetReplicaByNetworkID(id);
    if (!p)
        return TPtr<CActor>();
    return static_cast<ANetwork*>(p)->GetActor();
}

object CWorld::CreateGUID() {
    return msCreateGUIDFunc();
}

KH_BEGIN_GC(CWorld)
    //KH_GC_SHARED_PTR(thisx->mEngine)
    KH_GC_TPTR(thisx->mGame)
    KH_GC_OBJECT_EX(thisx->mLevels, dict)
KH_END_GC()

tuple CWorld::Pickler::getinitargs(CWorld& self) {
    return py::make_tuple();
}

tuple CWorld::Pickler::getstate(CWorld& self) {
    const int VERSION = 2;
    
    object levels, deflevel;
    if (!self.mIsUnbound) {
        levels = list(self.mLevels.attr("itervalues")());
        assert(len(levels) > 0);
        deflevel = object(self.mDefaultLevel);
    }

    return py::make_tuple(
        VERSION,
        self.mTicking,
        self.mInited,
        self.mGameStarted,
        self.mGame,
        self.mGameClass,
        levels,
        deflevel,
        self.mIsUnbound
        );
}

void CWorld::Pickler::setstate(CWorld& self, tuple state) {
    int version = extract<int>(state[0]);
    self.mTicking = extract<bool>(state[1]);
    self.mInited = extract<bool>(state[2]);
    self.mGameStarted = extract<bool>(state[3]);
    self.mGame = extract<TPtr<CGame>>(state[4]);
    self.mGameClass = state[5];
    object levels = extract<object>(state[6]);
    self.mDefaultLevel = extract<TPtr<CLevel>>(state[7]);
    if (version >= 2)
        self.mIsUnbound = extract<bool>(state[8]);
    
    TPtr<CWorld> selfp = self.GetSelfPtr<CWorld>();
    for (int i = 0; i < len(levels); i++) {
        object level = levels[i];
        TPtr<CLevel> levelptr = extract<TPtr<CLevel>>(level);
        self.mLevels[levelptr->GetName()] = level;
        self.mLevelHashMap[levelptr->GetNameHash()] = level;
        self.mLevelGUIDMap[levelptr->GetGUID()] = level;
        self.mLevelPtrs.push_back(levelptr.GetPtr());
        levelptr->AddedToWorld(selfp);
    }
        //mLevels[name] = level;
        //mLevelHashMap[levelptr->GetNameHash()] = level;
        //mLevelPtrs.push_back(levelptr.get());
}

void _InitWorldClasses() {
    _InitWorldBindings();

    object uuid = import("uuid");
    object cpickle = import("cPickle");

    CWorld::msCreateGUIDFunc = uuid.attr("uuid4");
    CWorld::msGUIDClass = uuid.attr("UUID");

    GRPCPickleFunc = cpickle.attr("dumps");
    GRPCPickleProtocol = cpickle.attr("HIGHEST_PROTOCOL");
    GRPCUnpickleFunc = cpickle.attr("loads");
}

void _ExitWorldClasses() {
    CWorld::msCreateGUIDFunc = object();
    CWorld::msGUIDClass = object();
    CWorld::msLevelClass = object();
    GRPCPickleFunc = object();
    GRPCPickleProtocol = object();
    GRPCUnpickleFunc = object();
}

}}